package ch.ethz.fcl.metrobuzz.spatialindex.object3d;

import java.util.ArrayList;
import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.data.scene.MBScene;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.data.scene.spacetime.Trip;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.LineSegment;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Polygon;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Rectangle;

public class PolygonTube implements IObject3D {
	public Polygon base;
	private float minZ;
	private float height;

	public PolygonTube() {
	}

	public PolygonTube(Polygon base, float zMin, float height) {
		this.base = base;
		this.minZ = zMin;
		this.height = height;
	}

	public Polygon getBase() {
		return base;
	}

	public float getMinZ() {
		return minZ;
	}

	public float getHeight() {
		return height;
	}

	@Override
	public BoundingBox getBoundingBox() {
		Rectangle bounds = base.getBound();
		return new BoundingBox(bounds.getMinX(), bounds.getMinY(), minZ,
				bounds.getMaxX(), bounds.getMaxY(), minZ + height);
	}

	@Override
	public boolean intersects(BoundingBox bb) {
		if (!base.intersects(new Rectangle(bb.getMinX(), bb.getMinY(), bb
				.getMaxX(), bb.getMaxY())))
			return false;

		if (bb.getMinZ() > minZ + height || bb.getMaxZ() < minZ)
			return false;

		return true;
	}

	public ArrayList<LineSegment3D> getIntersectLines(BoundingBox bb) {
		if (bb.getMinZ() > minZ + height || bb.getMaxZ() < minZ)
			return null;

		ArrayList<LineSegment3D> lines = new ArrayList<LineSegment3D>();

		ArrayList<Point2D> points = base.getIntersectPoints(new Rectangle(bb
				.getMinX(), bb.getMinY(), bb.getMaxX(), bb.getMaxY()));

		for (int i = 0; i < points.size(); i++) {
			Point3D p1 = new Point3D(points.get(i).getX(),
					points.get(i).getY(), Math.max(bb.getMinZ(), minZ));
			Point3D p2 = new Point3D(points.get(i).getX(),
					points.get(i).getY(), Math.min(bb.getMaxZ(), minZ + height));
			LineSegment3D line = new LineSegment3D(p1, p2, 0);
			lines.add(line);
		}

		return lines;
	}

	public boolean containPoint(Point3D p) {
		return base.contains(new Point2D(p.getX(), p.getY()))
				&& p.getZ() >= minZ && p.getZ() <= (minZ + height);
	}

	@Override
	public boolean contains(BoundingBox bb) {
		// Rectangle rectYZ = new
		// Rectangle(base.getBoundingRectangle().getMinY(),
		// zMin, base.getBoundingRectangle().getMaxY(), zMin + height);
		// Rectangle rectXZ = new
		// Rectangle(base.getBoundingRectangle().getMinX(),
		// zMin, base.getBoundingRectangle().getMaxX(), zMin + height);

		return base.contains(new Rectangle(bb.getMinX(), bb.getMinY(), bb
				.getMaxX(), bb.getMaxY()))
				&& minZ <= bb.getMinZ()
				&& minZ + height >= bb.getMaxZ();
		// && rectYZ.contains(new Rectangle(bb.getMinX(), bb.getMinZ(), bb
		// .getMaxX(), bb.getMaxZ()))
		// && rectXZ.contains(new Rectangle(bb.getMinY(), bb.getMinZ(), bb
		// .getMaxY(), bb.getMaxZ()));
	}

	public boolean inPeriod(float t0, float t1) {
		return minZ >= t0 && (minZ + height) <= t1;
	}

	public boolean containTrip(Trip.TripSegment trip, MBScene scene) {
		float time = (trip.getArrTime() - trip.getDepTime())
				/ trip.getNodesIndices().length;

		// return true if the trip is contained within the polygon tube
		for (int i = 0; i < trip.getNodesIndices().length; i++) {
			Node node = scene.getNodes().get(trip.getNodesIndices()[i]);
			if (!this.containPoint(new Point3D(node.getX(), node.getY(), trip
					.getDepTime() + time * i))) {
				return false;
			}
		}
		return true;
	}

	public ArrayList<Point3D> getIntersectPoint(Trip.TripSegment trip, MBScene scene) {
		float time = (trip.getArrTime() - trip.getDepTime())
				/ trip.getNodesIndices().length;

		if (trip.getArrTime() < this.minZ
				|| trip.getDepTime() > (this.minZ + this.height)
						+ this.getHeight())
			return null;

		ArrayList<Point3D> intersectPoints = new ArrayList<Point3D>();
		// return the intersection points
		for (int i = 0; i < trip.getNodesIndices().length - 1; i++) {
			Node node = scene.getNodes().get(trip.getNodesIndices()[i]);
			Node nextNode = scene.getNodes().get(trip.getNodesIndices()[i + 1]);

			ArrayList<Point3D> point = intersectLineSegment(
					new Point3D(node.getX(), node.getY(), trip.getDepTime()
							+ time * i),
					new Point3D(nextNode.getX(), nextNode.getY(), trip
							.getDepTime() + time * (i + 1)));

			if (point.size() != 0) {
				for (int j = 0; j < point.size(); j++)
					intersectPoints.add(point.get(j));
			}
		}
		return intersectPoints;
	}

	public ArrayList<Point3D> intersectLineSegment(LineSegment3D line) {
		return intersectLineSegment(line.getPoints()[0], line.getPoints()[1]);
	}

	// refrence: http://softsurfer.com/Archive/algorithm_0111/algorithm_0111.htm
	public ArrayList<Point3D> intersectLineSegment(Point3D p1, Point3D p2) {
		ArrayList<Point3D> result = new ArrayList<Point3D>();
		Point2D p1_2d = new Point2D(p1.getX(), p1.getY());
		Point2D p2_2d = new Point2D(p2.getX(), p2.getY());

		ArrayList<Point2D> xy_intersects = base.intersects(new LineSegment(
				p1_2d, p2_2d));

		for (int i = 0; i < xy_intersects.size(); i++) {
			Point2D p2d = xy_intersects.get(i);

			if (p2d != null)
				p2d.render();

			Point3D p3d = LineSegment.pointIn3D(p2d, p1, p2);

			if (p3d != null)
				result.add(p3d);
		}

		return result;
	}

	@Override
	public void render() {
		GL11.glColor3f(0.5f, 0.5f, 0.0f);
		GL11.glBegin(GL11.GL_LINES);
		ArrayList<Point2D> basePoints = base.getPoints();
		for (int i = 0; i < base.getNumPoints(); i++) {
			GL11.glVertex3d(basePoints.get(i).getX(), basePoints.get(i).getY(),
					minZ);
			GL11.glVertex3d(basePoints.get(i).getX(), basePoints.get(i).getY(),
					minZ + height);
		}

		for (int i = 0; i < base.getNumPoints() - 1; i++) {
			GL11.glVertex3d(basePoints.get(i).getX(), basePoints.get(i).getY(),
					minZ);
			GL11.glVertex3d(basePoints.get(i + 1).getX(), basePoints.get(i + 1)
					.getY(), minZ);
		}

		GL11.glVertex3d(basePoints.get(0).getX(), basePoints.get(0).getY(),
				minZ);
		GL11.glVertex3d(basePoints.get(base.getNumPoints() - 1).getX(),
				basePoints.get(base.getNumPoints() - 1).getY(), minZ);

		for (int i = 0; i < base.getNumPoints() - 1; i++) {
			GL11.glVertex3d(basePoints.get(i).getX(), basePoints.get(i).getY(),
					minZ + height);
			GL11.glVertex3d(basePoints.get(i + 1).getX(), basePoints.get(i + 1)
					.getY(), minZ + height);
		}

		GL11.glVertex3d(basePoints.get(0).getX(), basePoints.get(0).getY(),
				minZ + height);
		GL11.glVertex3d(basePoints.get(base.getNumPoints() - 1).getX(),
				basePoints.get(base.getNumPoints() - 1).getY(), minZ + height);

		GL11.glEnd();
	}
}
